/*
HEADER:         CUG999.03;
TITLE:          GED (nee QED) screen editor -- part 3;
DATE:           5/15/87;

DESCRIPTION:   "Screen output functions.

KEYWORDS:       screen, output;
SYSTEM:         MS-DOS;
FILENAME:       paint.c;
AUTHORS:        G. Nigel Gilbert, James W. Haefner, Mel Tearle, G. Osborn.
COMPILERS:      Microsoft 4.0
*/

/*
     e/qed/ged/se screen editor

    (C) G. Nigel Gilbert, MICROLOGY, 1981
           August-December 1981

    Modified:  Aug-Dec   1984:   BDS-C 'e'(vers 4.6a) to 'qe' (J.W. Haefner)
               March     1985:   BDS-C 'qe' to DeSmet-C 'qed' (J.W. Haefner)
               May       1986:   converted to ged - Mel Tearle

    FILE:      paint.c

    FUNCTIONS: putline, rewrite, putoffset, putstatusline, putlineno,
               putpage, putmess, unmess, putstr, calcoffset,
               resetcursor, setstatusname, error, error1, cerr, wait

    PURPOSE:   write text to screen
*/


#include <stdio.h>
#include "ged.h"


/* writes a line to the screen.   Uses the text address pointer in the third
 * argument instead of getline if the third argument is not NULL.
 *
 * On most systems the physical cursor will end at the end of the new
 * line.  It is not restored here because groups of lines are usually
 * written and it is more efficiet to repositon the cursor after the
 * group.
 */
putline(line,y,adr)
int  line, y;
char *adr;
{
    unsigned char  vbuf[SWIDTH+1];
    char *getline();
    int   bright, lastcol, off, i, j, x, sav;

    unsigned char  *p, c;
    int   cp, low, high;

    sav = curson(NO);
    vbuf[0] = '\0';


/* if block option and line in range  then reverse field */
    if ( blocking ) {

        if (vbord2 == 0)
            j = cline;
        else
            j = vbord2;

        if (vbord1 < j)
            i = vbord1;
        else {
            i = j;
            j = vbord1;
        }
        bright = (line >= i)  &&  (line <= j);
    }
    else {
        bright = NO;
    }

    if (adr == NULL) {
        if (altered && line == cline)
            cerr(32);  /* displayed data is obsolete */
        p = getline(line);
    }
    else
        p = adr;

    if (offset > 0) {
        lastcol = SWIDTH + offset;
        off = offset;
        if ( bright )  /* the markers are the opposite of the main color */
            colornorm();
        else
            colorblock();
        scr_aputch( 0, y, '<', ATTR2 );
        x = 1;
    }
    else {
        lastcol = SWIDTH;
        off = 0;
        x = 0;
    }

    if ( bright )
        colorblock();
    else
        colornorm();

    i = 0;
    for ( cp = 0; (*p)  &&  (cp < (lastcol)); p++, cp++ ) {
        if ( (off == 0) || (cp > off) )  {
            if ( *p >= 0 )      /* change limit to block control characters */
                vbuf[i++] = *p;
            else {
                if ( *p == '\t' ) {
                    do  {
                        if ( (off == 0) || (cp > off)  &&  (cp < (lastcol+x)) )
                            vbuf[i++] = ' ';
                    }
                    while ( (++cp % tabwidth) != 0 );
                    cp--;
                }
            }
        }
    }
    vbuf[i] = '\0';
    scr_putstr( x, y, vbuf, attr );

    if ( x + i < SWIDTH) {
        scr_delete( x + i, y );
    }
    else {
/*        if ( bright )
            colornorm();
        else
            colorblock();
  */
        c = *p;
        if (( c != '\0') && (cp == (lastcol))) {
            if ( *(++p) )
                scr_aputch( SWIDTH, y, '>', ATTR2 );
            else
                scr_aputch( SWIDTH, y, c, attr );
        }
        else {
            scr_aputch( SWIDTH, y, ' ', attr );
        }
    }
    colornorm();
    curson(sav);
    return;
}


/* rewrites current line from char 'cp', col 'x', onwards.  cp is the
 * character index, >= 0.  x is the virtual x cursor location.
 *
 * This routine could be called instead of rewrite to show line editing
 * if it is known that the new cursor will be on-screen.  The performance
 * improvement in doing so is insignificant.
 *
 * putline and this routine should be modified to have a greater commonality
 * than they do.
 */

rewrite1(cp,x)
int cp, x;
{
    unsigned char  vbuf[SWIDTH+1];
    int   h, i, j, k, begmark, sav;
    unsigned char  c;

    sav = curson(NO);
    vbuf[0] = '\0';
    h = i = j = k = 0;

    begmark = ( offset > 0 );
    if (begmark == 0)
        k = x;
    else {
        k = x - offset;
        if (k < 1)
            k = 1;
        scr_putstr( 0, cursory, "<", ATTR2 );
    }
    while ( x < SWIDTH+offset  &&  ( c = text[cp] ) )  {
        if ( c == '\t' )  {
            for ( i = tabwidth-x%tabwidth; i>0 && x<SWIDTH+offset-begmark; x++, i--)
                if ( x >= offset )
                    vbuf[j++] = ' ';
        }
/* change following limit to block control characters */
        else if ( (offset == 0) || ((x > offset)  &&  (c >= 0) ))
            vbuf[j++] = c;
        cp++;
        x++;
    }
    if (blocking)
        colorblock();
    else
        colornorm();

    vbuf[j] = '\0';
    scr_putstr( k, cursory, vbuf, attr );
    i = k + strlen(vbuf);
    if (i <= SWIDTH)
        scr_delete( i, cursory );

    c = text[cp];
    if ( (c > 0) && (x == SWIDTH+offset) ) {
        if(text[cp+1] == '\0' )
            scr_aputch( SWIDTH, cursory, c, attr );
        else
            scr_aputch( SWIDTH, cursory, '>' , ATTR2 );
    }
    curson(sav);
    return;
}


putoffset()
{
    gotoxy( 0, 0 );  /*?*/
    gotoxy( (offset>0), cursory );
}


putstatusline(line)
int line;
{
    int sav;
    char buf[50];

    sav = curson(NO);
    putlineno( line );
    strcpy(buf,justfile);
    strcat(buf,"                             ");
    buf[29]=0;   /* clear up to first time digit */
    scr_aputs( FNPOS, 0, buf, ATTR3 );

    show_time(1);
    curson(sav);
    blankedmess = NO;
    if (sav)
        resetpcursor();
    return;
}


/* uses scr_aputs to move the cursor else uspr won't work right.  call
 * resetpcursor after this call
 */
putlineno(line)
int line;
{
    int i, sav;

    if ( !displaypos )
        return;
    sav = curson(NO);
    scr_aputs( 0, 0, "       Line ", ATTR3 );

    displine = line;
    for ( i=5-uspr(displine, 0L, ATTR3); i > 0; i-- )
        scr_putch( ' ',ATTR3);

    scr_aputs( COLPOS-1, 0, " Col ", ATTR3 );

    dispcol = cursorx+1;
    for ( i=4-uspr(dispcol, 0L, ATTR3); i > 0; i-- )
        scr_putch( ' ',ATTR3 );
    if (charep) {
        scr_aputs(25,0,"   ",ATTR3);
        scr_aputs(28,0,"Over",ATTR2);
/* adjust space count to just before filename field */
        scr_aputs(32,0,"           ",ATTR3);
    }
    else
/* adjust space count to just before filename field */
        scr_aputs(25,0,"                  ",ATTR3);
    curson(sav);
    if (sav)
       resetpcursor();
    return;
}




/* exit help mode
 */
unmess()
{
    if (topline == 1)
        return;
    topline = 1;
    calp();
    putpage();
    return;
}

/* Write message to top line.  Uses all of line and clears the last
 * part.
 */
 putmess(s)
 char *s;
{
    int i;
    i = putmess1(s,0,SWIDTH+1);
    gotoxy(i+1,0);     /* position cursor for keyboard input */
    return;
}

/* write underlined message in status line.  Characters between || are
 * written highlighted.  xloc is the x cursor position of the first char.
 * width is the field width.  the trailing part if the field is cleared
 * if the message does not fill it.
 */
putmess1( s, xloc, width )
char *s;
int xloc, width;
{

    char c;
    int  i, cnt, norm;

    cnt = 0;
    gotoxy(abs(xloc), 0);
    norm = 0;
    color3();
    while( (c = *s++) ) {
        switch(c) {
        case '|':
            ( norm = ~norm ) ? color4() : color3();
            break;
        case '\n':
            break;
        default :
            scr_putch( c, attr );
            cnt += 1;
        }
    }
    color3();

    i = cnt;
    while (i++ < width)
        scr_putch( ' ', attr);
    colornorm();
    blankedmess = YES;
    return cnt;
}

/* Writes prompting/status messages to screen.  Characters between || are
 * written highlighted.
 */
int putstr(s)
char *s;
{
    int  norm;
    char c;

    norm = 0;
    colornorm();
    while( (c = *s++) ) {
        switch(c) {
        case '|':
            ( norm = ~norm ) ? color1() : colornorm();
            break;
        case '\n':
            putret();
            break;
        default :
            scr_putch( c, attr );
        }
    }
    colornorm();
    return;

}
/* insure that cursor is on screen with horizontal changes.  The argument is
 * the new cursorx that must be visible.  The algorithm uses hysteresis so
 * that horizontal scrolling only occurs when necessary.  If horizontal
 * scrolling gets behind the keyboard then the algorithm will jump ahead
 * twice as much as it finds itself behind.  The adaptive feature makes it
 * practical to scroll in increments of 1 on most display devices.
 */
calcoffset(x)
int x;
{
    int i, j;
    i = offset;

    if (x <= offset) {
        i = (x-1)/OFFWIDTH;
        i = i*OFFWIDTH;
        if (i<0)
            i=0;
    }
    j = strlen(text) > (offset+SWIDTH+1);  /*  1 if right marker needed */

    if (x > offset + SWIDTH -j) {
        i = x  - SWIDTH + j;
        i = i + (OFFWIDTH - 1);
        i = i - (i % OFFWIDTH);
    }

/* scroll horizontally in larger chunks if the program gets behind the
   keyboard
 */
    j = chkbuf();
    if (j > 0)
        while ( (j = chkbuf()) != chkbuf())  /* fill the char buffer */
            ;
    j = 2*j;  /* normally zero */
    if(j>20)
        j=20;
    if (i<offset)
        j=-j;
    if(i != offset)
        i=i+j;
    offset=i;
    if(offset<0)
        offset=0;
    return offset;
}

/* Display page.  Called only when a complete rewrite is necessary.
 * The help area and status line, as defined by topline, are unaffected,
 * except that the line and column displays are updated.
 */
putpage()
{
    int  i, j, y, line, sav;
    char c;
    sav = curson(NO);

/* The line number has normally changed when putpage is called */
    putlineno(cline);
    rw1();  /* calculate and hold offset */
    rw2();

    resetpcursor();
    curson(sav);
    return;
}


/* Page update.  Called after editing operations which affect screen
 * data and/or horizontal cursor position, but not involving a change
 * in cline.  The screen must already have been written,
 * as only changes are written.  The text buffer must contain
 * valid data.  Horizontal scrolling is performed as needed.
 *
 * Only the current line is scrolled horizontally if blockscroll is false.
 * That option is useful for slow remote terminals.  Operations which move the
 * cursor offscreen vertically will nevertheless result in a full
 * horizontal scroll when the entire page is eventually rewritten if
 * blockscroll is false.  The PC is fast enough that there is little
 * advantage to partial line updates.
 *
 * cp is the text[cp] index of the first character to be written.
 *
 * Call sync() first
 */

rewrite(cp)
int cp;
{
    if ( rw1() ) {
        if (blockscroll)
            rw2();         /* all text lines */
        else
            putline(cline, cursory, text);  /* all of cline */
    }
    else {
        rewrite1( cp, cursorx + (cp - charn) );  /* part of cline */
    }
    resetpcursor();  /* always onscreen at this point */
    return;
}

/* local function */
rw2()
{
    int line, y;

/* cline is put out in sequence with the rest for a good visual effect */
    for ( line = pfirst, y = topline; y <= SHEIGHT; line++, y++ ) {
        if ( line == cline ) {
            putline (line, y, text);
            if (y != cursory)
                cerr(33);
        }
        else if (line <= plast) {
            putline( line, y, NULL );
        }
        else {
            cleareol(0,y);
        }
    }
    return;
}


/* resetcursor() is used like rewrite, but is called when the screen change
 * is only a horizontal cursor position change.  The change may be
 * horizontally off-screen, in which case the entire screen is rewritten.
 * Call sync() first.
 *
 * moveline() handles vertical off-screen cursor movements, which may
 * also involve horizontal motion.
 */

resetcursor()
{

    if ( rw1() ) {
        if (blockscroll)
            rw2();         /* all text lines */
        else
            putline(cline, cursory, text);  /* all of cline */
    }
    resetpcursor();
    return;
}


/* reset physical cursor is a less general form of resetcursor.  It is used to
 * restore the cursor to the editing location when it is known that the
 * position is somewhere on the existing physical display.  That is
 * generally not true if the result of any operator command is being shown.
 */

resetpcursor()
{
    gotoxy( cursorx-offset, cursory );
    return;
}

/* local function */
rw1()
{
/* must insure screen current by putpage or otherwise if cline changes */
    if (displine != cline)
        cerr(34);  /* the text display may be wrong if no horz scroll occurs */

    if (dispcol != cursorx + 1) {
/* The column number has changed */
        putlineno(cline);
    }
/* compare offsets */
    if ( lastoff != calcoffset(cursorx) )  {
        lastoff = offset;
        return YES;  /* rewrite needed */
    }
    else {
        return NO;
    }
}



/* copy just filename w/o path for 'putstatusline' */

setstatusname()
{
    char *np, c;

    if ( filename[0] == '\0' )  {
        justfile[0] = '\0';
        return;
    }
    np = filename;
    while( *(np++) )     /* find end */
        ;
/* find last '\',if any  */
    while( ((c = *(--np)) != '\\')  &&  c != ':'  &&  (np != filename) )  {
        if ( c == '.' )  {      /* save any extension */
            if ( *(np+1) == '\0' )
                *(np) = '\0';
            strncpy(defext,np,4);  /* ".xxx\0"  */
        }
    }

    if ( !(*np) )  strcpy( justfile, filename );
    else  {
        memcpy(justfile, filename, 2 );
        strcpy( &justfile[2], ++np);
    }
}

/* Show an error message and wait.  The message kept up until the next
 * keystroke, then the key is used normally.  The purpose of the wait
 * is to prevent the status line from overwriting the message.  Use
 * error1 when the hold is not wanted.
 */
error(message)
char *message;
{
    int i;

    error1(message);
    i = chkbuf();
    while (chkbuf() == i)
        ;
    return;
}

/* Show an error and wait 3.0 to 4.0 seconds, or until a key is entered.
 */
errort(msg)
char *msg;
{

    error1(msg);
    wait(3);
    return;
}
/* Wait 'sec' seconds, or until a key is entered.  The amount of
 * delay is independent of the CPU speed.  The delay may be up to
 * one second more than specified.
 */
wait(sec)
int sec;
{
    int i, j, k, ck0;

    ck0 = chkbuf();

    i = gettime();
    for ( j = 0; j <= sec; j++) {
        while (i == (k = gettime())) {
            if (chkbuf() != ck0) {
                return;
            }
        }
        i = k;
    }
    return;
}

/* show an error message and return immediately.  used only when the
 * caller holds off the status line.  A call to hstart can be used
 * first to specify the first column number.  This call is good
 * for only one message.  The starting column is zero if the call is
 * not made.
 */

int bgin = 0;

hstart(col)
int col;
{
    bgin = col;
    return;
}
error1(message)
char *message;
{
    gotoxy(bgin,0);
    scr_aputs( bgin, 0, message, colorerr());
    cleareol( bgin+strlen(message), 0 );
    bgin = 0;
    blankedmess = YES;
    resetpcursor();
    colornorm();
    curson(YES);   /* always ready for operator input after an error */
    return;
}

/* Show an error message due to a coding error.
 */
cerr(errnum)
int(errnum);
{
    char buf[25];

    sprintf(buf, "Coding error %d", errnum);
    hstart(27);
    error(buf);
    return;
}
